「トレイトパラメータ」のご紹介:Scala 3のパワーアップした表現力(1)

Scala 3.3.4
最終更新:2020年7月15日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

この記事では、Scala 3(コードネーム:Dotty)から使用できるようになった「トレイトパラメータ」について解説します。

Scala 3ではトレイトにパラメータを持たせることができる

Scala 3からは、トレイトにパラメータを持たせることができるようになりました。

trait Result(val statusCode: Int, val statusCodeName: String) { override def toString = s"$statusCode $statusCodeName" def body: String }

このパラメータを「トレイトパラメータ」と呼びます。

また、トレイトパラメータを持つトレイトについての呼称はまだ定まっていないため、Scalapediaでは「パラメータ付きトレイト」と呼ぶこととします。

パラメータ付きトレイトは普通のトレイトと同様に使用できる

パラメータ付きトレイトは、普通のトレイトと同様に宣言し、使用することができます。

class ImATeapot(date: Date) extends Result(418, "I'm a teapot") { def body = "Sorry, I'm a teapot." } val result = new ImATeapot(Date.now) println(result.body) println(result.date)

初期化の順番は変わらない

パラメータ付きのトレイトであっても、評価の順番については通常のトレイトと同様に処理されます。

パラメータが評価されるタイミングは、そのパラメータを持つトレイトが評価される直前です。

パラメータを評価して、そのパラメータを使用してトレイトを評価するという順番です。

パラメータ付きトレイトの制約

パラメータ付きトレイトは、以下の3つの制約に従って使用されます。 まとめると、パラメータ付きトレイトに対しては「どこかで必ず一度だけクラスからパラメータを渡す必要がある」ということです。

1. パラメータ付きトレイトにパラメータを渡すことができるのは、クラスだけである

パラメータ付きトレイトにパラメータを渡すことができるのは、クラスだけです。
また、トレイトはパラメータ付きトレイトに対してパラメータを渡すことはできません。

つまり以下のような実装はできないということです。

trait ClientError extends Result(418, "I'm a teapot")

2. あるクラスCがパラメータ付きトレイトTを実装しているが、CのスーパークラスはTを実装していない場合、CTにパラメータを渡さなければならない

たとえば、ImATeapotクラスがパラメータ付きトレイトResultを実装しているとします。

もしImATeapotのスーパークラスBadRequestResultを実装していない場合、ImATeapotResultにパラメータを渡さなければならない、ということです。

以下の例をご覧ください。

これは不正な例です。
Resultに対してどのクラスもパラメータを渡していないことがわかります。

class ImATeapot extends BadRequest with Result { def body = "Sorry, I'm a teapot." } class BadRequest

これはOKな例です。

class ImATeapot extends BadRequest with Result(418, "I'm a teapot") { def body = "Sorry, I'm a teapot." } class BadRequest

あるいはImATeapotのかわりにBadRequestにてトレイトパラメータを渡すということもできます。

3. あるクラスCがパラメータ付きトレイトTを実装していて、かつCのスーパークラスもTを実装している場合、CはTにパラメータを渡すことはできない

たとえば、ImATeapotクラスがパラメータ付きトレイトResultを実装しているとします。

このうえでさらにImATeapotのスーパークラスBadRequestResultを実装している場合には、ImATeapotResultにパラメータを渡すことはできません。

これは不正な例です。
Resultに対して重複してパラメータを渡していることがわかります。

class ImATeapot extends BadRequest with Result(418, "I'm a teapot") { override def body = "Sorry, I'm a teapot." } class BadRequest extends Result(400, "Bad Request") { def body = s"$statusCode $statusCodeName" }

これはOKな例です。

class ImATeapot extends BadRequest with Result { override def body = "Sorry, I'm a teapot." } class BadRequest extends Result(400, "Bad Request") { def body = s"$statusCode $statusCodeName" }

あるいはBadRequestのかわりにImATeapotにてトレイトパラメータを渡すということもできます。

サイト内検索